﻿using System;
using System.Linq.Expressions;
using System.Reflection;
using Affinity.Core.Validation;

namespace Affinity.Synthesis.ExpressionCells.Helpers
{
    //TODO: Investigate whether this class adds any value and if not, remove.
    /// <summary>
    /// Helper class for invoking delegates in an efficient manner.
    /// </summary>
    public class DelegateInvoker
    {
        /// <summary>
        /// Factory method creating a DelegateInvoker for the given delegate.
        /// </summary>
        /// <param name="del"></param>
        /// <returns></returns>
        public static DelegateInvoker Create(Delegate del)
        {
            if (del.GetType().IsGenericType)
            {
                Type[] genericArguments = del.GetType().GetGenericArguments();

                Type delegateGenericTypeDefinition = del.GetType().GetGenericTypeDefinition();
                if (delegateGenericTypeDefinition == typeof(Func<>)) return (DelegateInvoker)Activator.CreateInstance(typeof(DelegateInvoker<>).MakeGenericType(genericArguments), del);
                if (delegateGenericTypeDefinition == typeof(Func<,>)) return (DelegateInvoker)Activator.CreateInstance(typeof(DelegateInvoker<,>).MakeGenericType(genericArguments), del);
                if (delegateGenericTypeDefinition == typeof(Func<,,>)) return (DelegateInvoker)Activator.CreateInstance(typeof(DelegateInvoker<,,>).MakeGenericType(genericArguments), del);
                if (delegateGenericTypeDefinition == typeof(Func<,,,>)) return (DelegateInvoker)Activator.CreateInstance(typeof(DelegateInvoker<,,,>).MakeGenericType(genericArguments), del);
                if (delegateGenericTypeDefinition == typeof(Func<,,,,>)) return (DelegateInvoker)Activator.CreateInstance(typeof(DelegateInvoker<,,,,>).MakeGenericType(genericArguments), del);
                if (delegateGenericTypeDefinition == typeof(Func<,,,,,>)) return (DelegateInvoker)Activator.CreateInstance(typeof(DelegateInvoker<,,,,,>).MakeGenericType(genericArguments), del);
            }

            return new DelegateInvoker(del);

            /*
            switch (genericArguments.Length)
            {
                case 1: return (DelegateInvoker)Activator.CreateInstance(typeof(DelegateInvoker<>).MakeGenericType(genericArguments), del);
                case 2: return (DelegateInvoker)Activator.CreateInstance(typeof(DelegateInvoker<,>).MakeGenericType(genericArguments), del);
                case 3: return (DelegateInvoker)Activator.CreateInstance(typeof(DelegateInvoker<,,>).MakeGenericType(genericArguments), del);
                case 4: return (DelegateInvoker)Activator.CreateInstance(typeof(DelegateInvoker<,,,>).MakeGenericType(genericArguments), del);

                default: return new DelegateInvoker(del);
            }
            */
        }

        /// <summary>
        /// Default constructor.
        /// </summary>
        protected DelegateInvoker()
        {
        }

        /// <summary>
        /// Constructor accepting the delegate to wrap.
        /// </summary>
        /// <param name="del"></param>
        private DelegateInvoker(Delegate del)
        {
            Assert.ArgumentNotNull(del, "del");
            _delegate = del;
        }

        /// <summary>
        /// Invokes the wrapped delegate with the given arguments.
        /// </summary>
        /// <param name="arguments"></param>
        /// <returns></returns>
        public virtual object Invoke(params object[] arguments)
        {
            return _delegate.DynamicInvoke(arguments);
        }

        private Delegate _delegate;
    }

    /// <summary>
    /// DelegateInvoker using no arguments and returning a value of type TResult.
    /// </summary>
    /// <typeparam name="TResult"></typeparam>
    internal class DelegateInvoker<TResult> : DelegateInvoker
    {
        /// <summary>
        /// Constructor accepting the delegate to wrap.
        /// </summary>
        /// <param name="del"></param>
        public DelegateInvoker(Delegate del)
        {
            _func = (Func<TResult>)del;
        }

        /// <summary>
        /// Invokes without using arguments, returns an object of type TResult.
        /// </summary>
        /// <param name="arguments"></param>
        /// <returns></returns>
        public override object Invoke(params object[] arguments)
        {
            return _func();
        }

        private Func<TResult> _func;
    }

    /// <summary>
    /// DelegateInvoker using one argument and returning a value of type TResult.
    /// </summary>
    /// <typeparam name="TParam"></typeparam>
    /// <typeparam name="TResult"></typeparam>
    internal class DelegateInvoker<TParam, TResult> : DelegateInvoker
    {
        /// <summary>
        /// Constructor accepting the delegate to wrap.
        /// </summary>
        /// <param name="del"></param>
        public DelegateInvoker(Delegate del)
        {
            _func = (Func<TParam, TResult>)del;
        }

        /// <summary>
        /// Invokes the wrapped delegate.
        /// </summary>
        /// <param name="arguments"></param>
        /// <returns></returns>
        public override object Invoke(params object[] arguments)
        {
            return _func((TParam)arguments[0]);
        }

        private Func<TParam, TResult> _func;
    }

    /// <summary>
    /// DelegateInvoker using two arguments and returning a value of type TResult.
    /// </summary>
    /// <typeparam name="TParam1"></typeparam>
    /// <typeparam name="TParam2"></typeparam>
    /// <typeparam name="TResult"></typeparam>
    internal class DelegateInvoker<TParam1, TParam2, TResult> : DelegateInvoker
    {
        /// <summary>
        /// Constructor accepting the delegate to wrap.
        /// </summary>
        /// <param name="del"></param>
        public DelegateInvoker(Delegate del)
        {
            _func = (Func<TParam1, TParam2, TResult>)del;
        }

        /// <summary>
        /// Invokes the wrapped delegate.
        /// </summary>
        /// <param name="arguments"></param>
        /// <returns></returns>
        public override object Invoke(params object[] arguments)
        {
            return _func((TParam1)arguments[0], (TParam2)arguments[1]);
        }

        private Func<TParam1, TParam2, TResult> _func;
    }

    /// <summary>
    /// DelegateInvoker using three arguments and returning a value of type TResult.
    /// </summary>
    /// <typeparam name="TParam1"></typeparam>
    /// <typeparam name="TParam2"></typeparam>
    /// <typeparam name="TParam3"></typeparam>
    /// <typeparam name="TResult"></typeparam>
    internal class DelegateInvoker<TParam1, TParam2, TParam3, TResult> : DelegateInvoker
    {
        /// <summary>
        /// Constructor accepting the delegate to wrap.
        /// </summary>
        /// <param name="del"></param>
        public DelegateInvoker(Delegate del)
        {
            _func = (Func<TParam1, TParam2, TParam3, TResult>)del;
        }

        /// <summary>
        /// Invokes the wrapped delegate.
        /// </summary>
        /// <param name="arguments"></param>
        /// <returns></returns>
        public override object Invoke(params object[] arguments)
        {
            return _func((TParam1)arguments[0], (TParam2)arguments[1], (TParam3)arguments[2]);
        }

        private Func<TParam1, TParam2, TParam3, TResult> _func;
    }

    /// <summary>
    /// DelegateInvoker using four arguments and returning a value of type TResult.
    /// </summary>
    /// <typeparam name="TParam1"></typeparam>
    /// <typeparam name="TParam2"></typeparam>
    /// <typeparam name="TParam3"></typeparam>
    /// <typeparam name="TParam4"></typeparam>
    /// <typeparam name="TResult"></typeparam>
    internal class DelegateInvoker<TParam1, TParam2, TParam3, TParam4, TResult> : DelegateInvoker
    {
        /// <summary>
        /// Constructor accepting the delegate to wrap.
        /// </summary>
        /// <param name="del"></param>
        public DelegateInvoker(Delegate del)
        {
            _func = (Func<TParam1, TParam2, TParam3, TParam4, TResult>)del;
        }

        /// <summary>
        /// Invokes the wrapped delegate.
        /// </summary>
        /// <param name="arguments"></param>
        /// <returns></returns>
        public override object Invoke(params object[] arguments)
        {
            return _func((TParam1)arguments[0], (TParam2)arguments[1], (TParam3)arguments[2], (TParam4)arguments[3]);
        }

        private Func<TParam1, TParam2, TParam3, TParam4, TResult> _func;
    }

    /// <summary>
    /// DelegateInvoker using five arguments and returning a value of type TResult.
    /// </summary>
    /// <typeparam name="TParam1"></typeparam>
    /// <typeparam name="TParam2"></typeparam>
    /// <typeparam name="TParam3"></typeparam>
    /// <typeparam name="TParam4"></typeparam>
    /// <typeparam name="TParam5"></typeparam>
    /// <typeparam name="TResult"></typeparam>
    internal class DelegateInvoker<TParam1, TParam2, TParam3, TParam4, TParam5, TResult> : DelegateInvoker
    {
        /// <summary>
        /// Constructor accepting the delegate to wrap.
        /// </summary>
        /// <param name="del"></param>
        public DelegateInvoker(Delegate del)
        {
            _func = (Func<TParam1, TParam2, TParam3, TParam4, TParam5, TResult>)del;
        }

        /// <summary>
        /// Invokes the wrapped delegate.
        /// </summary>
        /// <param name="arguments"></param>
        /// <returns></returns>
        public override object Invoke(params object[] arguments)
        {
            return _func((TParam1)arguments[0], (TParam2)arguments[1], (TParam3)arguments[2], (TParam4)arguments[3], (TParam5)arguments[4]);
        }

        private Func<TParam1, TParam2, TParam3, TParam4, TParam5, TResult> _func;
    }
}
